from .compat import complex_json


class App:
    def __init__(self,
                 broker,
                 tasks=None,
                 task_meta=None,
                 channels_subscribed=None,
                 exception_handler=None,
                 **kwargs):
        self.broker = broker
        self.channels_subscribed = channels_subscribed
        self.task_meta = task_meta
        self.tasks = tasks
        self.exception_handler = exception_handler

    @property
    def logger(self):
        return self.broker.logger

    async def start_publish(self):
        await self.broker.start_publish()
        self.logger.debug('ready to publish')

    async def start_consume(self):
        assert self.channels_subscribed is not None
        assert self.tasks is not None
        await self.broker.start_consume(
            self.channels_subscribed,
            self.consume_task,
        )

    async def send_task(self, task_name, task_arguments, *channel_args,
                        **channel_kwargs):
        task_data = {
            'task': task_name,
            'arguments': task_arguments,
            'meta': self.task_meta or {}
        }
        task_data = complex_json.dumps(task_data)
        await self.broker.publish(task_data, *channel_args, **channel_kwargs)

    async def consume_task(self, task_data):
        try:
            task_data = complex_json.loads(task_data)
            task_name = task_data['task']
            task_arguments = task_data['arguments']
            task_meta = task_data['meta']
        except complex_json.JSONDecodeError:
            self.logger.error('failed to decode task data: %s', task_data)
            return
        except KeyError:
            self.logger.error('malformed task data: %s', task_data)
            return
        task_handler = self.tasks.get(task_name)
        if not task_handler:
            self.logger.warning('unknown task: %s', task_name)
            return
        try:
            await self.execute_task(task_handler, task_arguments, task_meta)
        except Exception as err:
            exception_handler = self.exception_handler or \
                    self.default_exception_handler
            await exception_handler(task_name, err)

    async def execute_task(self, task_handler, task_arguments, task_meta):
        await task_handler(task_arguments, task_meta)

    async def default_exception_handler(self, task_name, err):
        self.logger.error(
            'error happened during task consume, task: %s, exc: %s',
            task_name,
            err,
            exc_info=True)

    async def stop(self):
        self.logger.debug('pubsub app now stop')
        await self.broker.stop()
